import time
import traceback

import pygame
from pygame.key import ScancodeWrapper
from pygame.locals import *

from game import RenderManager, TextureManager, LevelManager
from game.LevelManager import Level, move_block, is_block, move_player, is_wall, is_collide
from game.RenderManager import draw_level, draw_string, get_string_size
from game import LevelManager as lm
from game import GUI
from util import Logger as log


def start():
    log.set_level(log.LEVEL_DEBUG)
    log.info("Game -> starting")

    pygame_info = init_pygame("0.0.1")

    RenderManager.init(pygame_info)
    TextureManager.init()
    LevelManager.init()

    try:
        game_loop()
    finally:
        log.info("Game -> close")
        log.close()


def init_pygame(version: str):
    pygame.init()
    size = (20 * 16 * RenderManager.config.scale, 20 * 16 * RenderManager.config.scale)
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption(f"SokobanBox v{version} by ZHAOMH")
    return screen


loop = True
#clock = pygame.time.Clock()
max_fps = 63
fps = 0

def game_loop():
    global fps
    display(GUI.MainMenu())
    target_frame_time = 1.0 / max_fps
    fps_count= 0
    start_time = time.time()  # 循环开始的时间
    last_time = start_time  # 上一帧的时间
    update_interval = 1.0  # 每秒更新一次帧率显示
    while loop:
        #clock.tick(max_fps)
        try:
            tick()
        except:
            log.error(traceback.format_exc())
        fps_count += 1
        elapsed_time = time.time() - last_time
        if elapsed_time < target_frame_time:
            time.sleep(target_frame_time - elapsed_time)

        pygame.display.update()
        last_time = time.time()
        if (last_time - start_time) >= update_interval:
            fps = int(fps_count / (last_time - start_time))
            fps_count = 0
            start_time = last_time
    pygame.quit()


def display(gui: GUI.GUI | None):
    global display_gui
    log.info(f"Game -> display {gui.name if gui is not None else "close"}")
    if display_gui is not None:
        display_gui.close()
    display_gui = gui
    if gui is not None:
        gui.init()


display_gui: GUI.GUI | None = None


def quit():
    global loop
    loop = False


level: Level | None = None


def play_level(l: Level | None):
    global level
    level = l
    RenderManager.reload_gifs_cache()


key_tick = 0
key_tick_max = 5


def music():
    if not pygame.mixer.music.get_busy() == 1:  # 当音乐没有播放时
        pygame.mixer.music.load("./resources/sounds/music.ogg")
        pygame.mixer.music.set_volume(0.05)
        pygame.mixer.music.play()


def tick():
    global key_tick, closing_level
    if closing_level:
        closing_level = False
        play_level(None)
    music()
    RenderManager.update_cache()

    key_tick += 1

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            handle_mouse_down(event.button)
        elif event.type == pygame.MOUSEBUTTONUP:
            handle_mouse_up(event.button, pygame.mouse.get_pos())
        elif event.type == pygame.MOUSEMOTION:
            handle_mouse_motion(*pygame.mouse.get_pos())

    if key_tick >= key_tick_max:
        key_tick = 0
        handle_key_up(pygame.key.get_pressed())

    if level is not None:
        draw_level(level)
        draw_string(level.name, int((RenderManager.screen.get_width() - get_string_size(level.name, 25)[0]) / 2),
                    RenderManager.screen.get_height() - 35, "white", 25)

    if display_gui is not None:
        display_gui.render(RenderManager.screen)

    draw_string(f"FPS: {fps}", 8, RenderManager.screen.get_height() - 35, "white", 25)


closing_level: bool = False


def back_MainMenu():
    display(GUI.MainMenu())
    close_level()


def close_level():
    global closing_level
    closing_level = True


def handle_mouse_down(button: int):
    pass


def handle_mouse_up(button: int, pos: tuple[int, int]):
    if display_gui is not None:
        display_gui.on_mouse_up(button, pos)


def handle_mouse_motion(x: int, y: int):
    if display_gui is not None:
        display_gui.on_mouse_move(x, y)


def handle_key_up(keys: ScancodeWrapper):
    if display_gui is not None:
        for key in keys:
            display_gui.on_key(key)

    if level is not None:
        if keys[K_ESCAPE]:
            log.debug("key ESC pressed -> back MainMenu")
            back_MainMenu()

        player_move(keys)


player_animation = 0


def player_move(keys: ScancodeWrapper):
    right = (keys[K_d] or keys[K_RIGHT]) - (keys[K_a] or keys[K_LEFT])
    down = (keys[K_s] or keys[K_DOWN]) - (keys[K_w] or keys[K_UP])

    box = lm.is_box(level, level.player.x + right, level.player.y + down)

    if box is not None and not ((not right == 0) and (not down == 0)):
        if (0 <= level.player.x + right * 2 <= 19 and 0 <= level.player.y + down * 2 <= 19 and
                is_collide(level, level.player.x + right * 2, level.player.y + down * 2)):
            move_block(level, box, level.player.x + right * 2, level.player.y + down * 2)
            move_player(level, down, right)
    elif (lm.is_collide(level, level.player.x + right, level.player.y + down)
          and lm.is_collide(level, level.player.x + right, level.player.y)
          and lm.is_collide(level, level.player.x, level.player.y + down)):
        move_player(level, down, right)
